<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
	<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
	<meta http-equiv="pragma" content="no-cache" />
    <meta name="author" content="Jayli" />
	<meta name="keywords" content="淘宝UED 前端工程师 拔赤" />	
	<meta name="description" content="淘宝前端工程师拔赤，关注尖端前端技术，关注yui" />
	<meta http-equiv="imagetoolbar" content="no" />
	<meta name="title" content="拔赤的文章列表" />
    <title>Kissy loader的设计</title>
    <link href="http://jayli.github.com/blog/atom.xml" rel="alternate" title="setImpl" type="application/atom+xml" />
    <link rel="stylesheet" href="http://jayli.github.com/blog/media/css/style.css">
    <link rel="stylesheet" href="http://jayli.github.com/blog/media/css/github.css">
	<style>
	</style>
    <script type="text/javascript" src="http://jayli.github.com/blog/media/js/highlight.pack.js"></script>
    <script type="text/javascript">
      hljs.initHighlightingOnLoad();
    </script>
	<!--[if lte IE 8]>
	<script src="http://a.tbcdn.cn/apps/lottery/00023/index-v3/js/html5.js"></script>
	<![endif]-->
	<meta name="baidu-tc-verification" content="afb6c9df553ef3493d9ee65263df0d55" />
	<script src="http://siteapp.baidu.com/static/webappservice/uaredirect.js" type="text/javascript"></script><script type="text/javascript">uaredirect("http://m.zoojs.org", "http://jayli.github.com/blog");</script>
  </head>
  <body>
    <div id="container">
      <div id="main" role="main">
        <header>
        <h1>Kissy loader的设计</h1>
        </header>
        <nav>
        <span><a title="home page" class="" href="/blog/index.html">home</a></span>
        <span><a title="tags" class="" href="/blog/tags.html">tags</a></span>
        <span><a title="about" class="" href="/blog/about.html">about me</a></span>
        <span><a title="flickr" class="" href="http://www.flickr.com/photos/lijing00333">flickr</a></span>
        <span><a title="blogroll" class="" href="/blog/links.html">links</a></span>
        <span><a title="projects" class="" href="/blog/projects.html">projects</a></span>
        <span><a title="subscribe me" class="" href="/blog/atom.xml">feed</a></span>
        </nav>
        <article class="content">
        <section class="post">
<p>什么是KISSY</p>

<p>http://github.com/kissyteam/kissy</p>

<p>什么是loader</p>

<p>用过yui的人一定不会对yui-loader陌生，yui doc中对loader是如此描述的：</p>

<blockquote><p>The YUI Loader Utility is a client-side JavaScript component that allows you to load specific YUI components and their dependencies into your page via script. YUI Loader can operate as a holistic solution by loading all of your necessary YUI components, or it can be used to add one or more components to a page on which some YUI content already exists.</p></blockquote>

<p>所以，简单讲，loader就是一个处理js脚本依赖关系的加载器，实现了按需加载、延迟加载、加载相关联的js等功能，其中按需加载大大简化了开发web app的复杂度，延时加载使得domReady的时刻大大提前，飞速提升UI渲染速度，加载关联的js则使开发者不必去关注脚本排序，简化库的使用。颗粒化设计的库则必然需要loader来控制库的易用性，从yui2到yui3，这种趋势更加明显。因此，当KISSY开始走颗粒化的道路时，loader则几乎成了必须组件。</p>

<p>页面加载脚本的方式</p>

<p>如果页面中以script标签引入外部脚本，是阻塞式的加载，不管脚本加载完成先后，页面总是从上到下依次执行script脚本。如果是异步加载脚本，脚本加载完成后则立即执行脚本内容。因此，异步脚本的载入顺序和执行顺序是无关的，是和脚本载入完成的时刻有关系。而如果要处理异步脚本的依赖关系，除了要对脚本载入开始时刻进行排序，也要对载入完成后的回调进行排序。因此，脚本中的逻辑要写入回调函数中，以便排序只用。KISSY提供的简单沙箱可以模拟这种回调的包装。</p>

<p>多loader并存？</p>

<p>如果页面很复杂，我们很自然的期望每个沙箱拥有一个loader，loader之间的加载可以并行，每个loader只处理各自的依赖，这种设计是为了解决页面逻辑的解耦，页面中的每个app之间的耦合降到最低，整个页面不会因为一个app的加载障碍而影响其他app的运行，yui即是这种设计，这种设计最常用的场景是用在门户首页，页面没有固定的维护者，每个人都可以随时添加删除模块，页面最终渲染结果不受某个app的影响。因此在越复杂越变化频繁的页面中，多loader是一种先进的设计，而在普通页面中，多loader机制形同鸡肋。KISSY的设计宗旨是简单易用，因此，单loader模式基本可以满足目前国内门户的所有要求，而在配置combo的前提下，单loader的性能要远超过多loader的场景的。</p>

<p>沙箱之间的串接</p>

<p>库的设计离不开沙箱，沙箱除了用来做逻辑解耦，在loader机制中还扮演包装回调并串接执行回调的协调者角色。上面讲到，异步脚本是需要以回调的形式给出的，这样，loader就需要对脚本依赖进行排序，这里所说的脚本依赖，实际上是各个脚本回调函数的执行顺序，并不是严格意义的脚本加载（完成）顺序，即，严格讲，脚本加载顺序先后是和loader没必然关系的。</p>

<p>另外最重要的，KISSY被设计为弱沙箱，可以更清晰方便的管理回调的串接，但也正是因为弱沙箱，KISSY无法做到YUI沙箱中的独立实例设计所带来的灵活性。举个简单例子，YUI中的loader是可以嵌套的：</p>

<pre><code>YUI().use(‘mod1′,function(Y){
    YUI().use(‘mod2′,function(Y){
        //your code
    });
});
</code></pre>

<p>沙箱之间的scope相互不干扰，非常安全。KISSY作为一个全局“对象”（非构造器），是无法做到这一点的，因为多个scope受同一的全局KISSY 对象的影响。但这也涉及到刚才说的多loader的利弊，而且，我在使用YUI的实践中也从未这样写过代码，使用弱沙箱本来就应当保持简单的逻辑和苗条的身材，所以，保持弱沙箱的简洁易读要比用强沙箱去hack原本混乱的逻辑更干脆一些。因此，KISSY在全局作可以作为loader使用，在纯粹的串行逻辑中，依然保持其弱沙箱的本质。比如，作为loader使用：</p>

<pre><code>var S = KISSY;
S.add(‘mod1′,function(S){
    //scope 1
},{requires:['mod2']});
S.add(‘mod2′,function(){
    //scope 2
});
S.ready(function(S){
    //scope 3
});
</code></pre>

<p>作为简单沙箱使用：</p>

<pre><code>S.ready(function(S){
    S.add(‘mod1′,function(){
        //scope 1
    });
    S.ready(function(S){
        //scope 2
    });
});
</code></pre>

<p>这种代码很容易理解，而在YUI中，加载模块和引入回调是两个不同的概念，如果加载模块忘了引用，依赖关系实际上是断裂的，这种设计很容易导致初学者的思维混乱：为什么加载了脚本但脚本代码却不能用？比如：</p>

<pre><code>YUI().use(‘core’,function(Y){
    //Y.Node是不存在的，必须 use(‘node’)才可以
});
</code></pre>

<p>作为开发者就不明白为什么我引入了node.js，还要use(‘node’)，为什么要多此一举？YUI如此设计主要为了面向复杂的业务逻辑，也就是说，当页面业务逻辑本身复杂度到达一个层次之后，更深层次的解耦则显得十分必要。因此，YUI将“加载模块”作为第一层的解耦，将“引入回调”作为第二层的解耦，在复杂业务逻辑场景中有着无与伦比的优越性，因为这已经脱离了纯粹“架构”所做的事情，向更语义化的业务逻辑迈进了一步，我在使用YUI的过程中，对此深有体会。而在KISSY简洁的设计原则下，完全可以省掉这种在简单场景中略显累赘的鸡肋设计，复杂的业务逻辑会遇到，但不会天天遇到。更何况，KISSY提供KISSY.app来专门对业务逻辑进行支持，已经足够了。因此，框架层面的沙箱应当保持所见即所得的简洁，是符合KISSY的设计理念的。</p>

<p>页面渲染过程</p>

<p>页面在使用loader的加载过程中，渲染过程会有一些状态的变化，其中异步加载的脚本是需要特殊处理的，而除此阶段之外的脚本的执行，依然保持KISSY本初设计的原汁原味。因此这里增加了一个afterReady的状态，执行过程大致如此：</p>

<p>因此，KISSY loader的雏形也就差不多描述清楚了，简单沙箱、单loader、所见即所得的模块加载，另外，最终要的一点，不管是核心组件还是项目文件，一律当作模块处理，这是和YUI不同的地方。这样做combo会非常爽，任你颗粒化万千文件，页面中外加种子文件顶多只有三个http请求（css一个，js一个）。</p>

<p>以上～</p>

</section>
<section class="meta">

<!--span class="tags">
  tagged by 
  
</span-->

<span class="time">
  posted at <time datetime="2010-12-12">2010-12-12</time>
</span>
</section>

<section class="comment">
<div id="disqus_thread"></div>
<script type="text/javascript">
    /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
    var disqus_shortname = 'jayliblog'; // required: replace example with your forum shortname
	var disqus_identifier = 'urn:uuid:26929032-e45b-4de5-9d30-893ee81e5ef6';

    /* * * DON'T EDIT BELOW THIS LINE * * */
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
        dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</section>


        </article>
      </div>
    </div> <!--! end of #container -->
  </body>
</html>
